/**
 * 
 */
package com.module.hq.engine;

import java.awt.Point;

import com.dalonedrau.jogl.engine.GlobalConsts;
import com.dalonedrau.jogl.engine.Interactive;
import com.dalonedrau.jogl.engine.JOGLErrorHandler;
import com.dalonedrau.jogl.engine.Particle;
import com.dalonedrau.jogl.engine.Time;
import com.dalonedrau.jogl.util.UtilityMethods;
import com.dalonedrau.pool.PooledStringBuilder;
import com.dalonedrau.pool.StringBuilderPool;
import com.dalonedrau.vectors.Vector2;
import com.dalonedrau.vectors.Vector3;
import com.module.hq.bus.flyweights.HQTerrainTypes;
import com.module.hq.bus.flyweights.UltimaTileCell;
import com.module.hq.bus.globals.HQGlobals;
import com.module.hq.view.DungeonScreenView;

/**
 * Controller class for handling IO movement and actions related to movement on
 * the game map.
 * @author Donald
 */
public final class HQMovement {
	/** the one and only instance of the <code>HQMovement</code> class. */
	private static HQMovement	instance;
	/**
	 * Gives access to the singleton instance of {@link HQMovement}.
	 * @return {@link HQMovement}
	 */
	public static HQMovement getInstance() {
		if (HQMovement.instance == null) {
			HQMovement.instance = new HQMovement();
		}
		return HQMovement.instance;
	}
	/** the list of IOs that are actively traveling on the map. */
	private int[]	iosInMotion;
	/** the number of IOs that are actively traveling. */
	private int		numIOsInMotion;
	/** the id of the room being opened. */
	private int		roomOpening	= -1;
	/** Hidden constructor. */
	private HQMovement() {}
	/**
	 * Adds an IO to the list of IOs that are actively traveling on the map. If
	 * the IO is already on the list nothing is done; it won't be added twice.
	 * @param ioid the IO's reference id
	 */
	public void addIO(final int ioid) {
		if (iosInMotion == null) {
			iosInMotion = new int[0];
			numIOsInMotion = 0;
		}
		boolean found = false;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				found = true;
				break;
			}
		}
		if (!found) {
			int[] dest =
					new int[Math.max(iosInMotion.length, numIOsInMotion) + 1];
			System.arraycopy(iosInMotion, 0, dest, 0, numIOsInMotion);
			dest[numIOsInMotion] = ioid;
			iosInMotion = dest;
			dest = null;
			numIOsInMotion++;
		}
	}
	/**
	 * Gets the screen position of an IO that is moving along a path.
	 * @param ioid the IO's reference id
	 * @param zMod the modifier to the IO's z-level
	 * @return {@link Vector3}
	 * @throws Exception if an error occurs
	 */
	private Vector3 getAnimatedIOMove(final int ioid, final float zMod)
			throws Exception {
		Vector3 v = null;
		// x starts out at the left-side of the widget
		float x = (float) HQMap.getInstance().getPosition().getX();
		if (HQMap.getInstance().getBorder() != null) {
			x += HQMap.getInstance().getBorder().getInsets().left;
		}
		// y starts out at the bottom-side of the widget
		float y = (float) HQMap.getInstance().getPosition().getY();
		if (HQMap.getInstance().getBorder() != null) {
			y += HQMap.getInstance().getBorder().getInsets().bottom;
		}
		float z = (float) HQMap.getInstance().getPosition().getZ();
		z += (float) GlobalConsts.CHILD_STEP * zMod;
		// get the IO
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(
						ioid);
		// get the IO's map location
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		int cellX = pt.x;
		int cellY = pt.y;
		// check the time
		long now = Time.getInstance().getFrameStart();
		long animLastUpdate =
				io.getScript().getLocalVariable("moveLastUpdate").getLongVal();
		boolean animPaused =
				io.getScript().getLocalVariable("movePaused").getIntVal() > 0;
		long diff = now - animLastUpdate;
		if (animPaused) {
			// has it been 250ms since last update?
			long maxdiff =
					UtilityMethods.getInstance().convertMilliToNano(150);
			if (diff >= maxdiff) {
				// time to move again on next cycle
				animPaused = false;
				io.getScript().setLocalVariable("movePaused", new Integer(0));
				// reset the last update
				animLastUpdate = now;
				io.getScript().setLocalVariable(
						"moveLastUpdate", new Long(now));
			}
			HQMap.getInstance().centerMapOnCell(cellX, cellY);
			// just draw the IO on the map
			// add cell's column position
			x += cellX * HQMap.getInstance().getTileMapCellSize();
			// add cell's row position
			y += cellY * HQMap.getInstance().getTileMapCellSize();
			// adjust for viewport offset
			x -= HQMap.getInstance().getViewX();
			y -= HQMap.getInstance().getViewY();
			v = new Vector3(x, y, z);
		} else {
			// not paused. draw moving
			// check to see where the IO is, and move it 1 tile every 250ms
			// where is io right now?
			int destLoc =
					io.getScript().getLocalVariable("ioCurrentDest")
					.getIntVal();
			Point targ =
					UtilityMethods.getInstance().convertIntToPoint(destLoc);
			// has it been 350ms since last update?
			long maxdiff =
					UtilityMethods.getInstance().convertMilliToNano(350);
			if (diff >= maxdiff) {
				// reset the last update
				animLastUpdate = now;
				io.getScript().setLocalVariable(
						"moveLastUpdate", new Long(now));
				// IO made it to next tile.
				if (!isIOAtFinalDestination(io)) {
					HQMap.getInstance().moveIOToLocation(ioid, destLoc);
				}
				cellX = targ.x;
				cellY = targ.y;
				// is the next tile the final destination?
				if (isIOAtFinalDestination(io)) {
					int b = io.getScript().getLocalVariable(
							"playedEndTravelPause").getIntVal();
					boolean playedEndTravelPause = b > 0;
					if (playedEndTravelPause) {
						stopIOTravel(ioid);
					} else {
						animPaused = true;
						io.getScript().setLocalVariable(
								"movePaused", new Integer(1));
						playedEndTravelPause = true;
						io.getScript().setLocalVariable(
								"playedEndTravelPause", new Integer(1));
					}
				} else {
					// pause for a second and start moving again
					animPaused = true;
					io.getScript().setLocalVariable(
							"movePaused", new Integer(1));
					// set next target in path
					io.getScript().setLocalVariable(
							"ioCurrentDest", getNextTileInPath(ioid));
				}
				// just draw the IO on the map
				// add cell's column position
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new Vector3(x, y, z);
				HQMap.getInstance().centerMapOnIO(ioid);
			} else {
				// IO still moving. calculate new position
				// new x-pos modified by (new pos - old pos) * cell size
				float xMod = (targ.x - pt.x)
						* HQMap.getInstance().getTileMapCellSize();
				xMod *= (float) diff / (float) maxdiff;
				// new y-pos modified by (new pos - old pos) * cell size
				float yMod = (targ.y - pt.y)
						* HQMap.getInstance().getTileMapCellSize();
				yMod *= (float) diff / (float) maxdiff;
				// adjust IO's position based on map position and mods
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				x += xMod;
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				y += yMod;
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new Vector3(x, y, z);
			}
		}
		pt = null;
		return v;
	}
	/**
	 * Gets the screen position of a specific IO.
	 * @param ioid the IO's reference id
	 * @param zMod any modification the the z-axis applied
	 * @return {@link Vector3}
	 * @throws Exception if an error occurs
	 */
	public Vector3 getIOVector(final int ioid, final float zMod)
			throws Exception {
		Vector3 v = null;
		// x starts out at the left-side of the widget
		float x = (float) HQMap.getInstance().getPosition().getX();
		if (HQMap.getInstance().getBorder() != null) {
			x += HQMap.getInstance().getBorder().getInsets().left;
		}
		// y starts out at the bottom-side of the widget
		float y = (float) HQMap.getInstance().getPosition().getY();
		if (HQMap.getInstance().getBorder() != null) {
			y += HQMap.getInstance().getBorder().getInsets().bottom;
		}
		float z = (float) HQMap.getInstance().getPosition().getZ();
		z += (float) GlobalConsts.CHILD_STEP * zMod;
		// get the IO's map location
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		int cellX = pt.x;
		int cellY = pt.y;
		pt = null;
		// check to see if it's this IO's turn
		int currIO =
				HQScript.getInstance().getGlobalIntVariableValue("ioTurn");
		if (currIO == ioid) {
			// this is the IO whose turn it is - check for movement animation
			if (isIOInMotion(ioid)) {
				v = getAnimatedIOMove(ioid, zMod);
			} else {
				// just draw the IO on the map
				// add cell's column position
				x += cellX * HQMap.getInstance().getTileMapCellSize();
				// add cell's row position
				y += cellY * HQMap.getInstance().getTileMapCellSize();
				// adjust for viewport offset
				x -= HQMap.getInstance().getViewX();
				y -= HQMap.getInstance().getViewY();
				v = new Vector3(x, y, z);
			}
		} else {
			// this IO is not having a turn. just draw the IO on the map
			// add cell's column position
			x += cellX * HQMap.getInstance().getTileMapCellSize();
			// add cell's row position
			y += cellY * HQMap.getInstance().getTileMapCellSize();
			// adjust for viewport offset
			x -= HQMap.getInstance().getViewX();
			y -= HQMap.getInstance().getViewY();
			v = new Vector3(x, y, z);
		}
		return v;
	}
	/**
	 * Gets the next tile location in an IO's current travel path. If there is
	 * no next location, -1 is returned.
	 * @param ioid the IO's reference id
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	public int getNextTileInPath(final int ioid) throws Exception {
		int nextNode = -1;
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(ioid);
		int dest = io.getScript().getLocalVariable("ioFinalDest").getIntVal();
		Point destPt = UtilityMethods.getInstance().convertIntToPoint(dest);
		int[] path = HQDijkstraSearch.getInstance().getPathToTarget(destPt);
		for (int i = 0; i < path.length; i++) {
			if (HQMap.getInstance().getIOMapLocation(ioid) == path[i]
							&& i + 1 < path.length) {
				nextNode = path[i + 1];
				break;
			}
		}
		return nextNode;
	}
	/**
	 * Determines if a traveling IO has made it to its final destination.
	 * @param io the IO instance
	 * @return true if the IO is moving and reached its final destination; false
	 *         otherwise
	 * @throws Exception if an error occurs
	 */
	private boolean isIOAtFinalDestination(final HQInteractiveObject io)
			throws Exception {
		boolean madeIt = false;
		int dest = io.getScript().getLocalVariable("ioFinalDest").getIntVal();
		if (HQMap.getInstance().getIOMapLocation(io.getRefId()) == dest) {
			madeIt = true;
		}
		return madeIt;
	}
	/**
	 * Determines if an IO is traveling on the map.
	 * @param ioid the IO's reference id
	 * @return true if the IO is traveling; false otherwise
	 */
	public boolean isIOInMotion(final int ioid) {
		boolean is = false;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				is = true;
				break;
			}
		}
		return is;
	}
	/**
	 * Opens a door.
	 * @param moc
	 */
	public void openDoor(final int moc) {
		try {
			// change the cell type to open
			int open = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_door_open_to_floor");
			HQMap.getInstance().setMapCellType(moc, open);
			// reset movement paths
			HQMap.getInstance().resetTravelLocations();
			// find the room being opened
			int[] rooms = HQMap.getInstance().getRoomsForCell(moc);
			if (rooms.length != 2) {
				Point pt =
						UtilityMethods.getInstance().convertIntToPoint(
								moc);
				Exception ex = new Exception(
						"Invalid map entry. Cell "
								+ pt
								+ " leads to "
								+ rooms.length
								+ " rooms. Only 2 rooms possible.");
				JOGLErrorHandler.getInstance().fatalError(ex);
			}
			// a door is in two rooms. find the one room the PC
			// hasn't visited yet
			HQInteractiveObject pc =
					HQController.getInstance().getPCObject();
			PooledStringBuilder sb =
					StringBuilderPool.getInstance()
							.getStringBuilder();
			sb.append(HQScript.getInstance()
					.getGlobalStringVariableValue(
							"currentMapName"));
			sb.append("_roomsVisited");
			int[] visited = pc.getScript().getLocalVariable(
					sb.toString()).getIntArrayVal();
			boolean room0 = true, room1 = true;
			roomOpening = rooms[1];
			for (int i = 0; i < visited.length; i++) {
				if (visited[i] == rooms[0]) {
					room0 = false;
				}
				if (visited[i] == rooms[1]) {
					room1 = false;
				}
			}
			if (room0 && room1) {
				Point pt =
						UtilityMethods.getInstance().convertIntToPoint(
								moc);
				Exception ex = new Exception(
						"Invalid map entry. PC hasn't visited "
								+ "either room that Cell "
								+ pt
								+ " leads to.");
				JOGLErrorHandler.getInstance().fatalError(ex);
			} else if (!room0 && !room1) {
				/*
				 * Point pt = UtilityMethods.getInstance().convertIntToPoint(
				 * moc); Exception ex = new Exception(
				 * "Invalid map entry. PC has visited " +
				 * "both rooms that Cell " + pt + " leads to.");
				 * JOGLErrorHandler.getInstance().fatalError(ex);
				 */
			} else if (room0) {
				roomOpening = rooms[0];
			}
			visited = null;
			rooms = null;
			pc = null;
			sb.returnToPool();
			// open the room to view - play animation
			HQScript.getInstance().setGlobalVariable(
					"lastRoomPlayerOpened", roomOpening);
			startOpeningRoom();
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	 /** 
	  * the flag indicating whether the PC's turn should end after performing 
	  * a search. 
	  */
	private boolean endTurnAfterSearch;
	/**
	 * Reveals a set of secret doors in a room.
	 * @param doors the locations of all doors revealed
	 * @param roomId the room's id
	 * @param flag flag indicating that the PC's turn should end
	 * after performing the search.
	 */
	public void revealSecretDoors(final int[] doors, final int roomId, 
			final boolean flag) {
		try {
			// change the cell types to closed doors
			int closed = HQTerrainTypes.getInstance().getTerrainId(
					"ultima_door_closed_to_floor");
			for (int i = 0; i < doors.length; i++) {
				HQMap.getInstance().setMapCellType(doors[i], closed);
			}
			roomOpening = roomId;
			// open the room to view - play animation
			HQScript.getInstance().setGlobalVariable(
					"lastRoomPlayerOpened", roomOpening);
			startRevealingSecretDoors(doors);
			endTurnAfterSearch = flag;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Plays the room opening animation. 1. For the first 2 seconds, a particle
	 * emitter should throw fog particles all over the room. 2. after the room
	 * has been exposed, the camera should shift back to center on the PC.
	 */
	public void playAnimationRoomOpening() {
		try {
			final long now = Time.getInstance().getFrameStart();
			HQInteractiveObject pc = HQController.getInstance().getPCObject();
			// room opening. check to see if panning to room center is needed
			if (roomOpening == -1) {
				JOGLErrorHandler.getInstance().fatalError(
						new Exception("Room id being opened was never set."));
			}
			// has the panning reached completion?
			boolean needsPanning = true;
			// how long has the animation been running?
			long animStart =
					HQScript.getInstance().getGlobalLongVariableValue(
							"roomOpenStart");
			final long diff =
					UtilityMethods.getInstance().convertNanoToMilli(
							now - animStart);
			if (diff > Particle.EMITTER_MAP_FOG_LIFESPAN) {
				// room is exposed and emitter is done. pan camera to center on
				// pc
				boolean finished = HQMap.getInstance().panToIO(pc.getRefId());
				if (finished) {
					// the animation is finished
					roomOpening = -1;
					// set map mode to free
					HQMap.getInstance().setMode(
							GlobalConsts.MAP_MODE_00_FREE);
					HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
				}
			} else if (diff > HQMap.ANIMATION_ROOM_EXPOSE_AFTER) {
				// after 450ms, copy new room to global array
				// so it appears slowly exposed
				boolean visited = HQPlayer.getInstance().hasPlayerVisitedRoom(
						pc, roomOpening);
				if (!visited) {
					HQPlayer.getInstance().addRoomVisited(pc, roomOpening);
				}
			}
			pc = null;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Plays the room opening animation. 1. For the first 2 seconds, a particle
	 * emitter should throw fog particles all over the room. 2. after the room
	 * has been exposed, the camera should shift back to center on the PC.
	 */
	public void playAnimationRevealSecretDoors() {
		try {
			System.out.println("playAnimationRevealSecretDoors");
			final long now = Time.getInstance().getFrameStart();
			HQInteractiveObject pc = HQController.getInstance().getPCObject();
			// room opening. check to see if panning to room center is needed
			if (roomOpening == -1) {
				JOGLErrorHandler.getInstance().fatalError(
						new Exception("Room id being opened was never set."));
			}
			// has the panning reached completion?
			boolean needsPanning = true;
			// how long has the animation been running?
			long animStart =
					HQScript.getInstance().getGlobalLongVariableValue(
							"roomOpenStart");
			final long diff =
					UtilityMethods.getInstance().convertNanoToMilli(
							now - animStart);
			if (diff > Particle.EMITTER_MAP_FOG_LIFESPAN) {
				// room is exposed and emitter is done. pan camera to center on
				// pc
				boolean finished = HQMap.getInstance().panToIO(pc.getRefId());
				if (finished) {
					System.out.println("animation finished - panning finished");
					// the animation is finished
					roomOpening = -1;
					// set map mode to free
					HQMap.getInstance().setMode(
							GlobalConsts.MAP_MODE_00_FREE);
					HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
					if (endTurnAfterSearch) {
						System.out.println("ending io turn");
						endTurnAfterSearch = false;
						HQTurn.getInstance().nextIOTurnCycle();
					}
				}
			} else if (diff > HQMap.ANIMATION_ROOM_EXPOSE_AFTER) {
				// after 450ms, copy new room to global array
				// so it appears slowly exposed
				boolean visited = HQPlayer.getInstance().hasPlayerVisitedRoom(
						pc, roomOpening);
				if (!visited) {
					HQPlayer.getInstance().addRoomVisited(pc, roomOpening);
				}
			}
			pc = null;
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Processes the room animation.
	 * @throws Exception if an error occurs
	 */
	private void processRoomAnimation() throws Exception {
		// the room opens in a 3-step process
		// 1. center on the room
		// 2. expose the room over 350ms
		// 3. pan back down to the player

		// is the room still being exposed?// check the time
		long now = Time.getInstance().getFrameStart();
		long animLastUpdate =
				HQScript.getInstance().getGlobalLongVariableValue(
						"roomOpenLastUpdate");
		long diff = now - animLastUpdate;
		// has it been 350ms since last update?
		long maxdiff =
				UtilityMethods.getInstance().convertMilliToNano(350);
		if (diff < maxdiff) {
			// center map on the room.
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// expose the room
		} else {}
	}
	/**
	 * Removes an IO from the list of IOs in motion. If the IO is not in motion,
	 * then it will not be removed.
	 * @param ioid the IO's reference id
	 */
	public void removeIO(final int ioid) {
		if (iosInMotion == null) {
			iosInMotion = new int[0];
			numIOsInMotion = 0;
		}
		int index = -1;
		for (int i = 0; i < numIOsInMotion; i++) {
			if (iosInMotion[i] == ioid) {
				index = i;
				break;
			}
		}
		if (index > -1) {
			numIOsInMotion--;
			if (index == 0) {
				// remove first index
				int[] dest = new int[numIOsInMotion];
				System.arraycopy(iosInMotion, 1, dest, 0, numIOsInMotion);
				iosInMotion = dest;
				dest = null;
			} else if (index == numIOsInMotion) {
				// remove the last index
				int[] dest = new int[numIOsInMotion];
				System.arraycopy(iosInMotion, 0, dest, 0, numIOsInMotion);
				iosInMotion = dest;
				dest = null;
			} else {
				// remove middle index
				int[] dest = new int[numIOsInMotion];
				// copy up until index to be removed
				System.arraycopy(iosInMotion, 0, dest, 0, index);
				// copy all after index to be removed
				System.arraycopy(iosInMotion, index + 1, dest, index,
						numIOsInMotion - index);
				iosInMotion = dest;
				dest = null;
			}
		}
	}
	/** Starts the room opening sequence. */
	private void startOpeningRoom() {
		try {
			// center map on room
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// set map mode to animation
			HQMap.getInstance().setMode(
					GlobalConsts.MAP_MODE_03_MOVE_ANIMATION);
			// set start time
			HQScript.getInstance().setGlobalVariable("roomOpenStart",
					new Long(Time.getInstance().getFrameStart()));
			HQScript.getInstance().setGlobalVariable("roomOpenLastUpdate",
					new Long(Time.getInstance().getFrameStart()));
			HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_02_ROOM_OPENING);
			// start the panning timer to start immediately
			// startPanningTimer(Time.getInstance().getGameTime()
			// - GlobalConsts.PANNING_START_DELAY);
			// take 2000ms to expose room
			// add emitters on all room cells
			int roomId = HQScript.getInstance().getGlobalIntVariableValue(
					"lastRoomPlayerOpened");
			UltimaTileCell[] cells = HQMap.getInstance().getRoom(roomId);
			for (int i = 0; i < cells.length; i++) {
				// get the cell's center point, without adjusting for the map's
				// onscreen position
				Vector2 v = HQMap.getInstance().getCellCenterPoint(
						cells[i].getX(), cells[i].getY());
				// adjust center point for the viewing offset
				v.move(-HQMap.getInstance().getViewX(),
						-HQMap.getInstance().getViewY());
				Particle.getInstance().addEmitter(
						HQMap.getInstance(),
						Particle.PARTICLE_MAP_FOG,
						new Vector3(v.getX(), v.getY(),
								HQMap.getInstance().getPosition().getZ()
										+ (float) GlobalConsts.CHILD_STEP
										* HQMap.Z_LEVEL_IO_HIGHLIGHTS));
			}
			cells = null;
			// take 350 ms to center on the room
			// take 350 ms to expose all room cells
			// take 300 ms to pause
			// center back on the player
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Starts the sequence to reveal all secret doors in a room.
	 * @param doors the list of cell's containing the secret doors
	 */
	private void startRevealingSecretDoors(final int[] doors) {
		try {
			System.out.println("**startRevealingSecretDoors");
			// center map on room
			HQMap.getInstance().centerMapOnRoom(roomOpening);
			// set map mode to animation
			HQMap.getInstance().setMode(
					GlobalConsts.MAP_MODE_03_MOVE_ANIMATION);
			// set start time
			HQScript.getInstance().setGlobalVariable("roomOpenStart",
					new Long(Time.getInstance().getFrameStart()));
			HQScript.getInstance().setGlobalVariable("roomOpenLastUpdate",
					new Long(Time.getInstance().getFrameStart()));
			HQMap.getInstance().setCurrentAnimation(
					HQMap.ANIM_03_SHOW_SECRET_DOORS);
			// start the panning timer to start immediately
			// startPanningTimer(Time.getInstance().getGameTime()
			// - GlobalConsts.PANNING_START_DELAY);
			// take 2000ms to expose room
			// add emitters on all room cells
			int roomId = HQScript.getInstance().getGlobalIntVariableValue(
					"lastRoomPlayerOpened");
			for (int i = 0; i < doors.length; i++) {
				// get the cell's center point, without adjusting for the map's
				// onscreen position
				Vector2 v = HQMap.getInstance().getCellCenterPoint(doors[i]);
				// adjust center point for the viewing offset
				v.move(-HQMap.getInstance().getViewX(),
						-HQMap.getInstance().getViewY());
				Particle.getInstance().addEmitter(
						HQMap.getInstance(),
						Particle.PARTICLE_MAP_FOG,
						new Vector3(v.getX(), v.getY(),
								HQMap.getInstance().getPosition().getZ()
										+ (float) GlobalConsts.CHILD_STEP
										* HQMap.Z_LEVEL_IO_HIGHLIGHTS));
			}
		} catch (Exception ex) {
			JOGLErrorHandler.getInstance().fatalError(ex);
		}
	}
	/**
	 * Stops an IO's movement.
	 * @param ioid the IO's reference id
	 * @throws Exception if an error occurs
	 */
	private void stopIOTravel(final int ioid) throws Exception {
		PooledStringBuilder sb =
				StringBuilderPool.getInstance().getStringBuilder();
		HQInteractiveObject io =
				(HQInteractiveObject) Interactive.getInstance().getIO(ioid);
		int prof = io.getPCData().getProfession();
		sb.append(HQGlobals.CLASS_TITLES[prof]);
		sb.append(" moved to ");
		Point pt = UtilityMethods.getInstance().convertIntToPoint(
				HQMap.getInstance().getIOMapLocation(ioid));
		sb.append(pt.x);
		sb.append(",");
		sb.append(pt.y);
		pt = null;
		sb.append(".\n");
		DungeonScreenView.getInstance().sendPlayerMessage(sb.toString());
		sb.returnToPool();
		io.getScript().setLocalVariable("ioCurrentDest", new Integer(-1));
		io.getScript().setLocalVariable("ioFinalDest", new Integer(-1));
		io.getScript().setLocalVariable("moveStart", new Long(0));
		io.getScript().setLocalVariable("moveLastUpdate", new Long(0));
		io.getScript().setLocalVariable("movePaused", new Integer(0));
		io.getScript().setLocalVariable("playedEndTravelPause", new Integer(0));
		HQMap.getInstance().setCurrentAnimation(HQMap.ANIM_00_NONE);
		removeIO(ioid);
		// reset movement paths
		HQMap.getInstance().resetTravelLocations();
		if (ioid == HQController.getInstance().getPcId()) {
			// reset map
			HQMap.getInstance().setMode(GlobalConsts.MAP_MODE_00_FREE);
		}
	}
}
